package com.vidmt.api.common.utils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Utility class to perform common String manipulation algorithms.
 */
public final class HashUtils {

	private static final Logger Log = LoggerFactory.getLogger(HashUtils.class);
	private static Map<String, MessageDigest> digests = new ConcurrentHashMap<>();

	/**
	 * Hashes a byte array using the specified algorithm and returns the result
	 * as a String of hexadecimal numbers. This method is synchronized to avoid
	 * excessive MessageDigest object creation. If calling this method becomes a
	 * bottleneck in your code, you may wish to maintain a pool of MessageDigest
	 * objects instead of using this method.
	 * <p>
	 * A hash is a one-way function -- that is, given an input, an output is
	 * easily computed. However, given the output, the input is almost
	 * impossible to compute. This is useful for passwords since we can store
	 * the hash and a hacker will then have a very hard time determining the
	 * original password.
	 * </p>
	 * <p>
	 * In Jive, every time a user logs in, we simply take their plain text
	 * password, compute the hash, and compare the generated hash to the stored
	 * hash. Since it is almost impossible that two passwords will generate the
	 * same hash, we know if the user gave us the correct password or not. The
	 * only negative to this system is that password recovery is basically
	 * impossible. Therefore, a reset password method is used instead.
	 * </p>
	 *
	 * @param bytes
	 *            the byte array to compute the hash of.
	 * @param algorithm
	 *            the name of the algorithm requested.
	 * @return a hashed version of the passed-in String
	 */
	public static String hash(String data, String salt, String algorithm) {
		if (salt == null) {
			salt = "";
		}
		byte[] saltbytes = salt.getBytes(StandardCharsets.UTF_8);
		byte[] databytes = data.getBytes(StandardCharsets.UTF_8);
		byte[] bytes = null;
		try {
			ByteArrayOutputStream buff = new ByteArrayOutputStream(saltbytes.length + databytes.length);
			buff.write(saltbytes);
			buff.write(databytes);
			bytes = buff.toByteArray();
			buff.close();
		} catch (IOException e) {
			e.printStackTrace();
		}

		synchronized (algorithm.intern()) {
			MessageDigest digest = digests.get(algorithm);
			if (digest == null) {
				try {
					digest = MessageDigest.getInstance(algorithm);
					digests.put(algorithm, digest);
				} catch (NoSuchAlgorithmException nsae) {
					Log.error("Failed to load the " + algorithm + " MessageDigest. "
							+ "Jive will be unable to function normally.", nsae);
					return null;
				}
			}
			// Now, compute hash.
			digest.update(bytes);
			return CommUtil.encBase64Url(digest.digest());
		}
	}
}